﻿package skuatools.statistics 
{
	import flash.text.TextField;
	import mx.core.UIComponent;

	public class BoxPlot extends UIComponent
	{
		private var _data:Array;
		
		//internal values
		private var iqr:Number = new Number();
		private var l_bound:Number = new Number();
		private var r_bound:Number = new Number();
		private var l_outliers:Array = new Array();
		private var r_outliers:Array = new Array();
		
		//public values		
		public var median:Number = new Number();
		public var q1:Number = new Number();
		public var q3:Number = new Number();
		public var l_whisk:Number = new Number();
		public var r_whisk:Number = new Number();
		public var max:Number;
		
		public function BoxPlot(data:Array)
		{
			this._data = data;
			super();
		}
		
		override protected function createChildren():void{
			super.createChildren();
			init();
		}
		
		override protected function measure():void{
			super.measure();
		}
		
		private function init():void {
			
			_data.sort(Array.NUMERIC);
			_data.reverse();
			
			//remove zeros
			while (_data[_data.length - 1] == 0) 
			{
				_data.pop();
			} 
			
			_data.reverse();
			
			var half:Number = Math.round(_data.length / 2);
			var quar:Number = (_data.length / 4);
		
			//median and quartile calculations
			//still need to come up with a better, conclusive method
			if(_data.length % 2 == 0) 
			{
				//even
				
				median = (_data[half - 1] + _data[half]) / 2;
				
				if(half % 2 == 0) 
				{
					//halves composed of an even number of values
					//q would be equal to the average of the two half middle values
					q1 = (_data[ Math.round(quar - 1) ] + _data[ Math.round(quar) ]) / 2;
					
					q3 = (_data[ Math.round(half) + Math.round(quar - 1)] + _data[ Math.round(half) + Math.round(quar)]) / 2
				}
				else 
				{
					//odd
					//q would be equal to middle value
					q1 = _data[ Math.round(quar - 1) ];
					
					q3 = _data[ Math.round(half) + Math.round(quar - 1) ];
				}
				
			} 
			else 
			{
				//odd
				
				median = _data[Math.round(half - 1)];

				if( (half - 1) % 2 == 0) 
				{
					//halves composed of an even number of values
					//q would be equal to the average of the two half middle values
					q1 = (_data[ Math.round(quar - 1) ] + _data[ Math.round(quar) ]) / 2;
					
					q3 = (_data[ Math.round(half) + Math.round(quar - 1)] + _data[ Math.round(half) + Math.round(quar)]) / 2
				} 
				else 
				{
					//odd
					//q would be equal to middle value
					q1 = _data[ Math.round(quar - 1) ];
					
					q3 = _data[ Math.round(half) + Math.round(quar - 1) ];
				}
			}
			
			//calculate IQR
			iqr = q3 - q1;
			
			//calculate 1.5*IQR
			var iqr1_5:Number = iqr*1.5;
			
			//calculate 3.0*IQR
			var iqr3_0:Number = iqr*3.0;
			
			//calculate Left Bound (1.5 IQR less than Q1)
			l_bound = q1 - iqr1_5;
			
			//calcualte Right Bound (1.5 IQR greater than Q3)
			r_bound = q3 + iqr1_5;
			
			drawLeft();
			
		}
		private function drawLeft():void 
		{
			//bin left outliers and whisker
			
			for(var i:int = 0;i < _data.length;i++) 
			{
				
				if(_data[i] < l_bound) 
				{
					l_outliers.push(_data[i]);
				}
				if(_data[i] > l_bound && _data[i] < q1) 
				{
					l_whisk = _data[i];
					break;
				}
				
			}
				
			if(l_whisk == 0 && l_outliers.length == 0) 
			{
				l_whisk = _data[0];
			} 
			else if (l_whisk == 0 && l_outliers.length > 0) 
			{
				l_whisk = _data[l_outliers.length]
			}
			
			drawRight();
		}
		private function drawRight():void {
			//bin right outliers and whisker
			
			_data.reverse();
			
			for(var i:int = 0;i < _data.length;i++) 
			{
				if(_data[i] > r_bound) 
				{
					r_outliers.push(_data[i]);
				} 
				else if(_data[i] < r_bound && _data[i] > q3) 
				{
					r_whisk = _data[i];
					break;
				}
			}
			
			if(r_whisk == 0 && r_outliers.length == 0) 
			{
				r_whisk = _data[0];
			} else if (r_whisk == 0 && r_outliers.length > 0) 
			{
				r_whisk = _data[r_outliers.length];
			}
			
			built();
		}
		private function built():void {
			
			_data.reverse();
			
			//background
			var bg:UIComponent = new UIComponent();
			bg.graphics.beginFill(0xFFFFF3);
			bg.graphics.drawRoundRect(-15,30,width,-100,20);
			bg.alpha = 1;
			addChild(bg);
			
			var scale:Number = new Number(width/12);
			max = new Number(_data[_data.length - 1]);
			var right_end:Number = new Number( Math.round(max*1.1) );
			var increment:Number = new Number( Math.round(right_end/10) );		
			var breaks:Array = new Array(1,5,10,15,20,25,50,100,250,500,1000,1500,2500);
			
			if(increment == 0) 
			{
				increment = 1;
			} 
			else 
			{
				for(var h:int = 0;h < breaks.length;h++) 
				{
					if(increment % breaks[h] == increment) 
					{
						increment = breaks[h];
						break;
					}
				}	
			}
			
			//number line
			var line:UIComponent = new UIComponent();
			line.graphics.lineStyle(1);
			line.graphics.moveTo(0,0);
			line.graphics.lineTo( scale*10 , 0 );
			addChild(line);
			
			for(var i:int = 0;i < 11; i++) 
			{
				var hash:UIComponent = new UIComponent();
				hash.graphics.lineStyle(1);
				hash.graphics.moveTo(0 + i*scale,3);
				hash.graphics.lineTo(0 + i*scale,-3);
				addChild(hash);
				
				var hash_label:TextField = new TextField();
				var num_label:Number = new Number(0 + i*increment );
				hash_label.text = Math.round(num_label).toString();
				hash_label.y = 4;
				hash_label.x = -3 + (i*scale);
				addChild(hash_label);
			}
			
			//draw IQR box
			if(iqr > 0) 
			{
				var iqr_box:UIComponent = new UIComponent();
				iqr_box.graphics.lineStyle(1);
				iqr_box.graphics.beginFill(0xD9D9D9);
				iqr_box.graphics.drawRect( (q1/increment)*scale,-60,(iqr/increment)*scale,50);
				addChild(iqr_box);
			}
			
			//draw left whisker and line
			if(q1 > 0) 
			{
				var left_line:UIComponent = new UIComponent();
				left_line.graphics.lineStyle(1);
				left_line.graphics.moveTo( (q1/increment)*scale ,-35);
				left_line.graphics.lineTo( (l_whisk/increment)*scale , -35);
				left_line.graphics.lineTo( (l_whisk/increment)*scale , -50);
				left_line.graphics.lineTo( (l_whisk/increment)*scale , -20);
				addChild(left_line);
				
				//draw right whisker and line
				var right_line:UIComponent = new UIComponent();
				right_line.graphics.lineStyle(1);
				right_line.graphics.moveTo( (q3/increment)*scale ,-35);
				right_line.graphics.lineTo( (r_whisk/increment)*scale , -35);
				right_line.graphics.lineTo( (r_whisk/increment)*scale , -50);
				right_line.graphics.lineTo( (r_whisk/increment)*scale , -20);
				addChild(right_line);
			}
			
			//draw median
			var l_median:UIComponent = new UIComponent();
			l_median.graphics.lineStyle(3, 0x000000, 1);
			l_median.graphics.moveTo( 0 + (median/increment)*scale, -10)
			l_median.graphics.lineTo( 0 + (median/increment)*scale, -60)
			addChild(l_median);
			
			//draw right outliers
			if(r_outliers.length!=0) 
			{
				for(var j:int = 0;j < r_outliers.length;j++) 
				{
					var r_out:UIComponent = new UIComponent();
					r_out.graphics.lineStyle(1,0xB2182B,1);
					r_out.graphics.beginFill(0xB2182B);
					r_out.graphics.drawCircle( (r_outliers[j]/increment)*scale,-35,2);
					r_out.alpha = 0.4;
					addChild(r_out);
				}
			}
			
			//draw left outliers
			if(l_outliers.length!=0) 
			{
				for(var k:int = 0;k < l_outliers.length;k++) 
				{
					var l_out:UIComponent = new UIComponent();
					l_out.graphics.lineStyle(1,0x2166AC,1);
					l_out.graphics.beginFill(0x2166AC);
					l_out.graphics.drawCircle( (l_outliers[k]/increment)*scale,-35+(i*4),2);
					l_out.alpha = 0.4;
					addChild(l_out);
				}
			}

			//done drawing
		}
	}
}